///-----------------------------------------------------------------------------
/// Filename: XmlController.cs
///
/// Description: 
///  Contains the classes used in the basic MVC controller stage. The controller
///  itself is an XML aggregator conroller meaning it builds up the Model from
///  a number of XML documents.
///
/// History:
/// 24 Jan 2004	Aaron Clauson	Created.
/// 04 Sep 2004 Aaron Clauson	Added the no cache directive to the HTTP response.
/// 09 Sep 2004	Aaron Clauson	Added a more detailed exception message when a
///								controller class cannot be instantiated.
///	08 Nov 2004	Aaron Clauson	Added an implicit load of a system.xml file to
///								the models. Used for system wide settings required
///								by view stages such as web paths.
///	28 Nov 2004	Aaron Clauson	Added pagepath setting to model to indicate whether 
///								SSL pages should use the secure path or not.
///
/// License: 
/// Public Domain.
///-----------------------------------------------------------------------------

using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using BlueFace.Sys;
using MaverickLite.Config;
using MaverickLite.Model;
using MaverickLite.View;
using log4net;

namespace MaverickLite.Controller
{
	public class XmlController : ControllerProduct
	{
		private const string XML_CONTENT_TYPE = "text/xml";
		public const string DEFAULT_VIEW_NAME = "success";
		
		private ArrayList m_aggregatorModelPaths = null;	// Relative paths to the xml models and whether they are overwrite or not.
		protected XmlAggregatorModel m_pageModel;			// Aggregator model which will is the basis of all building block models.
		protected bool m_addSystemModel = true;				// Determines whether to add the system XML to the page model.

		/// <summary>
		/// The constructor sets up the controller parameters and builds the view workflow.
		/// </summary>
		/// <param name="initParams">The xml element containing the controller's intial parameters.</param>
		public XmlController(XmlElement initParams)
		{			
			try
			{	
				//logger.Debug("XmlController created.");

				// Clone the node containing the initial controller parameters, just in case
				// a derived controller wants to check or do some custom processing.
				InitialParameters = (XmlElement)initParams.Clone();

				#region Extract the childless node parameters into the simple parameters hashtable.

				// Load all the simple parameter nodes - those without children - into a hashtable.
				foreach(XmlNode parameterNode in initParams.SelectNodes(CONTROLLER_PARAM_KEY))
				{
					// Check if this is a childless (aka simple) parameter.
					if(parameterNode.ChildNodes.Count == 0)
					{
						string parameterName = parameterNode.Attributes.GetNamedItem(CONTROLLER_PARAM_NAME_ATTRIBUTE).Value;
						string parameterValue = parameterNode.Attributes.GetNamedItem(CONTROLLER_PARAM_VALUE_ATTRIBUTE).Value;

						// Insert name value pair into the parameters hashtable.
						if(parameterName != null && parameterName.Trim() != String.Empty)
						{
							this.SimpleParameters.Add(parameterName, parameterValue);
						}
					}		
				}

				#endregion

				#region Extracting the model paths.

				// Add the xml documents from the controller initialisation nodes to the model.
				XmlNode pathNode = initParams.SelectSingleNode(MODEL_NODE_NAME);
				if(pathNode != null)
				{
					m_aggregatorModelPaths = new ArrayList();
					
					// Add the paths to the hashtable in reverse so that the model gets built using the top node first.
					for(int modelNodesCount=1; modelNodesCount<=pathNode.ChildNodes.Count; modelNodesCount++)
					{
						string modelPathNodeStr = null;
						
						try
						{
							XmlNode modelPathNode = pathNode.SelectSingleNode("*[position() = " + modelNodesCount + "]");
							if(modelPathNode != null)	// If the node is a comment it will be null.
							{
								modelPathNodeStr = modelPathNode.OuterXml;

								bool overwrite = false;
								if(modelPathNode.Attributes.GetNamedItem("overwrite") != null)
								{
									overwrite = Convert.ToBoolean(modelPathNode.Attributes.GetNamedItem("overwrite").Value);
								}
							
								m_aggregatorModelPaths.Add(new DictionaryEntry(modelPathNode.Attributes.GetNamedItem("value").Value, overwrite));
								//logger.Debug("Aggregator model path added, " + modelPathNode.Attributes.GetNamedItem("value").Value + ".");
							}
						}
						catch(Exception excp)
						{
							logger.Debug("Exception XmlController (adding model path): " + modelPathNodeStr + ". " + excp.Message);
						}
					}	
				}

				#endregion

				#region Load views into the view map.

				// Dictionary to load views into.
				ViewMap = new Hashtable();

				// Load views.
				foreach(XmlElement viewNode in initParams.SelectNodes(VIEW_NODE_NAME)) 
				{
					string viewName = DEFAULT_VIEW_NAME;
					if(viewNode.Attributes.GetNamedItem(VIEW_NAME_ATTRIBUTE)!= null)
					{
						viewName = viewNode.Attributes.GetNamedItem(VIEW_NAME_ATTRIBUTE).Value;
					}

					//logger.Info("Creating view: " + viewName);

					//logger.Debug(viewNode.OuterXml);

					ViewProduct view = ViewFactory.CreateView(viewNode);
					ViewMap.Add(viewName.ToLower(), view);
				}

				#endregion
			}
			catch(Exception excp)
			{
				throw new ApplicationException("Exception XmlController: " + excp.Message);
			}
		}

		
		public override void Go(HttpContext context)
		{
			logger.Debug("XMLController Started: " + DateTime.Now.ToString("dd MMM yyyy HH:mm:ss:fff"));
			
			base.Go(context);

			m_pageModel = new XmlAggregatorModel(m_specialParameters);	// Create a clean model for this page.
			
			// Add useful data that is common for all requests to the model for use in the view transforms.
			AddSystemDataToModel();
			
			InitialiseRequest(context);

			logger.Debug("XMLController Initialise Finished: " + DateTime.Now.ToString("dd MMM yyyy HH:mm:ss:fff"));

			// Perform any actions required by the request. If a string is returned indicates that the client
			// should be sent a redirect request.
			string redirectPath = Perform();

			if(redirectPath == null)
			{
				// Add the models from the controller initialisation parameters.
				if(!m_pageModel.AddToModel(m_aggregatorModelPaths, m_physicalApplicationPath))
				{
					//logger.Warn("No model paths were provided to the aggregator.");
				}

				// Add the request specific models to the page model.
				BuildPageModel();

				logger.Debug("XMLController BuildPageModel Finished: " + DateTime.Now.ToString("dd MMM yyyy HH:mm:ss:fff"));
				
				// Get the view for the request.
				string pageView = GetView();
				if(pageView != null && pageView.Trim() != String.Empty)
				{
					m_view = pageView;
				}

				// For pages that we don't want anyone to cache.
				context.Response.Cache.SetCacheability(HttpCacheability.NoCache);

				// Find and process the view.
				if(ViewMap.Count == 0)
				{
					// If a controller has no views send the model to the client as xml.
					context.Response.ContentType = XML_CONTENT_TYPE;
					
					// Write the model to the response stream without a view stage.
					XmlTextWriter finalStageWriter = new XmlTextWriter(context.Response.OutputStream, Encoding.UTF8);
				
					// ToDo: Look into how the DocType affects browser behaviour and how well it is supported.
					//finalStageWriter.WriteDocType("HTML", "http://www.w3.org/TR/html4/loose.dtd", "-//W3C//DTD HTML 4.0 Transitional//EN", null);
					
					// Commented this line as it was breaking PBX GUI in FireFox. AC 1 Nov 2006.
					//finalStageWriter.Formatting = MavAppState.OutputFormatting;
					m_pageModel.GetModel().WriteTo(finalStageWriter);
					finalStageWriter.Flush();

					context.Response.End();
				}
				else
				{
					logger.Debug("XMLController ViewStage Started: " + DateTime.Now.ToString("dd MMM yyyy HH:mm:ss:fff"));
					
					ViewProduct viewStage = (ViewProduct)ViewMap[m_view.ToLower()];
					if(viewStage == null)
					{
						throw new ApplicationException("Requested view, " + m_view + ", could not be located in the controller's view map.");
					}
					else
					{
						logger.Debug("View = " +  m_view + ".");
						//logger.Debug("Controller handing over to view, " + m_view + ", for rendering.");
						viewStage.Go(m_pageModel.GetModel(), context.Response); 
					}
				}
			}
			else
			{
				Redirect(context, redirectPath);
			}
		}


		protected void Redirect(HttpContext context, string redirectPath)
		{
			logger.Debug("The Controller specified a client redirect to " + redirectPath + ".");
			
			foreach(DictionaryEntry specialParameter in m_specialParameters)
			{
				redirectPath = Regex.Replace(redirectPath, Regex.Escape((string)specialParameter.Key), (string)specialParameter.Value);
			}
			
			context.Response.Redirect(redirectPath, true);
		}


		protected virtual void InitialiseRequest(HttpContext context)
		{
			//logger.Debug("XmlController InitialiseRequest.");
			
			m_view = "success";	
		}

		/// <summary>
		/// Template method that should do any processing required on the request and session state.
		/// </summary>
		/// <returns>If a string is returned from this method indicates a client redirect should be sent.</returns>
		public virtual string Perform()
		{
			return null;
		}


		/// <summary>
		/// Template method that should be used to construct the page specific model for the request.
		/// </summary>
		public virtual void BuildPageModel()
		{
			logger.Debug("XmlController InitialiseRequest.");
			
			// Do nothing.
		}


		/// <summary>
		/// Template method that should return the view for the page.
		/// </summary>
		/// <returns>A string used to determine which view map is used.</returns>
		public virtual string GetView()
		{
			return null;
		}


		/// <summary>
		/// Add system level information to the model that will be useful in the view transforms.
		/// </summary>
		/// <param name="isSecure">True if the client HTTP request was made over SSL.</param>
		public void AddSystemDataToModel()
		{
			if(m_addSystemModel)
			{
				if(m_systemDataNode != null && m_systemDataNode.DocumentElement != null)
				{
					m_pageModel.AddToModel(m_systemDataNode.DocumentElement, true);
				}

				// Add some page paths that are always useful
				m_pageModel.AddToModel("<page><system><pagepath>" + base.m_pagePath + "</pagepath></system></page>", false);
				m_pageModel.AddToModel("<page><system><webapppath>" + base.m_webAppPath + "</webapppath></system></page>", false);
				m_pageModel.AddToModel("<page><system><securepath>" + base.m_securePath + "</securepath></system></page>", false);
				m_pageModel.AddToModel("<page><path>" + MavRequestState.Path + "</path></page>", true);
				m_pageModel.AddToModel("<page><pagename>" + Regex.Match(MavRequestState.Path, @".*\/(?<pagename>\w+)\..*").Result("${pagename}") + "</pagename></page>", true);
				m_pageModel.AddToModel("<page><requestid>" + Guid.NewGuid() + "</requestid></page>", true);
				m_pageModel.AddToModel("<page><requesttimeutc>" + DateTime.Now.ToUniversalTime().ToString("dd MMM yyyy HH:mm:ss") + "</requesttimeutc></page>", true);
                m_pageModel.AddToModel("<page><random>" + Crypto.GetRandomInt(1).ToString() + "</random></page>", true);
			}
		}


		/// <summary>
		/// Adds an error message element to the model.
		/// </summary>
		/// <param name="message">The error message to add to the model.</param>
		public void AddErrorMessage(string message)
		{
			logger.Error(message);
			m_pageModel.AddAdditionalToModel("<page><error><![CDATA[" + message + "]]></error></page>", false);
		}
	}
}
